#!/usr/bin/env python3

# Copyright (c) Facebook, Inc. and its affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

from enum import Enum, auto
from typing import (
    Any,
    AnyStr,
    Dict,
    Iterator,
    List,
    Mapping,
    Optional,
    Tuple,
    TypeVar,
    Union,
)

# Convenience
Attrs = Dict[str, Any]
PayloadGroup = Mapping[int, Union[str, bytes]]
Directories = Mapping[str, "Directory"]
lsn_t = int

# everything here is copied from the BOOST_PYTHON_MODULE in
# ~/fbcode/logdevice/clients/python/logdevice_client.cpp
# not everything is copied, but I have attempted to be as comprehensive as possible

class LogGroup:
    name: str
    fully_qualified_name: str
    version: int
    range: Tuple[int, int]
    attrs: Attrs

class Directory:
    children: Directories
    logs: Mapping[str, LogGroup]
    attrs: Attrs
    # TODO flesh out

class DataRecord:
    logid: int
    lsn: lsn_t
    timestamp: float
    payload: Any
    payloads: Dict

class Reader:
    def __iter__(self) -> Iterator: ...
    def __next__(self) -> Tuple[Any, Any]: ...
    def stop_iteration(self) -> bool: ...
    def start_reading(self, logid: int, from_: lsn_t, until_: lsn_t) -> bool: ...
    def stop_reading(self, logid: int) -> bool: ...
    def is_connection_healthy(self, logid: int) -> bool: ...
    def without_payload(self) -> bool: ...

# client API
class Client:
    def __init__(
        self,
        name: str,
        config: str,
        timeout: int,
        settings: Dict,
        credentials: Optional[str] = None,
        csid: Optional[str] = None,
    ) -> None: ...
    def append(self, logid: int, data: Union[AnyStr, PayloadGroup]) -> lsn_t: ...
    def create_reader(self, max_logs: int) -> Reader: ...
    def data_size(self, logid: int, start_sec: float, end_sec: float) -> int: ...
    def find_key(self, logid: int, key: str) -> Tuple[int, int]: ...
    def find_time(self, logid: int, seconds: float) -> lsn_t: ...
    def get_directory(self, path: str) -> Directory: ...
    def get_directory_delimiter(self) -> str: ...
    def get_head_attributes(self, logid: int) -> Tuple[lsn_t, int]: ...
    def get_log_group_by_id(self, id: int) -> LogGroup: ...
    def get_log_group_by_name(self, path: str) -> LogGroup: ...
    def get_log_range_by_name(self, path: str) -> Tuple[int, int]: ...
    def get_max_payload_size(self) -> int: ...
    def get_tail_attributes(self, logid: int) -> Tuple[lsn_t, int, Any]: ...
    def get_tail_lsn(self, logid: int) -> lsn_t: ...
    def is_log_empty(self, logid: int) -> bool: ...
    def make_directory(
        self, path: str, mk_intermediate_dirs: bool, attrs: Attrs
    ) -> Directory: ...
    def make_log_group(
        self,
        path: str,
        start_id: int,
        end_id: int,
        attrs: Attrs,
        mk_intermediate_dirs: bool,
    ) -> LogGroup: ...
    def remove_directory(self, path: str, recursive: bool) -> int: ...
    def remove_log_group(self, path: str) -> int: ...
    def rename(self, old_path: str, new_path: str) -> int: ...
    def set_attributes(self, path: str, attrs: Attrs) -> int: ...
    def set_log_group_range(self, path: str, from_id: int, to_id: int) -> int: ...
    def set_timeout(self, timeout: float) -> None: ...
    def smoke_test(self, log_ids: List[int], count: int) -> None: ...
    def smoke_test_read(self, starting_points: Dict[int, int], count: int) -> None: ...
    def smoke_test_write(self, logids: List[int], count: int) -> Dict[int, int]: ...
    def sync_logsconfig_version(self, version: int) -> bool: ...
    def trim(self, logid: int, lsn: int) -> None: ...

# constants
BYTE_OFFSET_INVALID: int
LOGID_INVALID: int
LOGID_MAX: int
LSN_INVALID: int
LSN_OLDEST: int
LSN_MAX: int

# time
def timestr_to_seconds(time: str) -> int: ...
def timestr_to_milliseconds(time: str) -> int: ...
def seconds_to_timestr(seconds: int) -> str: ...
def milliseconds_to_timestr(millis: int) -> str: ...

# error codes:
# not even close to comprehensive! please add them as you need them
# i wonder if we can parse the errors.inc file here...
class status(Enum):
    EXISTS = auto()
    ID_CLASH = auto()
    NOTFOUND = auto()
    UNROUTABLE = auto()

class LogDeviceError(Exception): ...

# logging
class LoggingLevel(Enum):
    NONE = auto()
    CRITICAL = auto()
    ERROR = auto()
    WARNING = auto()
    NOTIFY = auto()
    INFO = auto()
    DEBUG = auto()
    SPEW = auto()

def getLoggingLevel() -> LoggingLevel: ...
def setLoggingLevel(level: LoggingLevel) -> None: ...
def lsn_to_string(lsn: lsn_t) -> str: ...
def validate_json(main: str, logs: str, verbose: bool) -> bool: ...
def normalize_json(main: str, logs: str, verbose: bool) -> bool: ...
def parse_log_level(value: str) -> LoggingLevel: ...
def use_python_logging(cb: Any) -> None: ...  # what kind of object is this?
def set_log_fd(fd: int) -> int: ...
def get_internal_log_name(int): ...
def is_internal_log(int): ...
